import Konva from 'konva'
import { KonvaEventObject } from 'konva/lib/Node'
import { useEffect, useRef, useState } from 'react'
import { Layer, Stage, Star, Text } from 'react-konva'

function generateShapes() {
  return [...Array(20)].map((_, i) => ({
    id: i.toString(),
    x: Math.random() * window.innerWidth,
    y: Math.random() * window.innerHeight,
    rotation: Math.random() * 180,
    isDragging: false
  }))
}

const INITIAL_STATE = generateShapes()

type ICursorStatus = 'cursor-auto' | 'cursor-grab'

export default function View() {
  const [stars, setStars] = useState(INITIAL_STATE)

  // 拖动开始
  const handleDragStart = e => {
    const id = e.target.id()
    setStars(
      stars.map(star => {
        return {
          ...star,
          isDragging: star.id === id
        }
      })
    )
  }

  // 拖动结束
  const handleDragEnd = () => {
    setStars(
      stars.map(star => {
        return {
          ...star,
          isDragging: false
        }
      })
    )
  }

  const [stageX, setStageX] = useState(0)
  const [stageY, setStageY] = useState(0)
  const stageRef = useRef<Konva.Stage>(null)
  // 鼠标状态
  const [cursorStatus, setCursorStatus] = useState<ICursorStatus>('cursor-auto')
  // 当前坐标
  const stageXRef = useRef(0)
  const stageYRef = useRef(0)
  // 开始的坐标
  const startStageXRef = useRef(0)
  const startStageYRef = useRef(0)
  // 上一个在stage内的坐标
  const lastStageXRef = useRef(0)
  const lastStageYRef = useRef(0)
  // 滚轮拖动开始位置
  const wheelStartXRef = useRef(0)
  const wheelStartYRef = useRef(0)
  // 鼠标点击了画布
  const mouseClickRef = useRef(false)

  // 鼠标滚轮事件
  const handleWheel = (e: KonvaEventObject<WheelEvent>) => {
    e.evt.preventDefault()

    const deltaY = e.evt.deltaY

    if (e.evt.altKey) {
      // 缩放画布
      const direction: '放大' | '缩小' = deltaY > 0 ? '缩小' : '放大'
      const pointer = stageRef.current.getPointerPosition()
      const scaleBy = 1.05 // 缩放幅度
      const oldScale = stageRef.current!.scaleX()

      const mousePointTo = {
        x: (pointer.x - stageRef.current!.x()) / oldScale,
        y: (pointer.y - stageRef.current!.y()) / oldScale
      }
      const newScale = direction === '放大' ? oldScale * scaleBy : oldScale / scaleBy
      // 限制缩放倍数
      if (newScale <= 0.1 || newScale >= 5) {
        return
      }
      const newPos = {
        x: pointer.x - mousePointTo.x * newScale,
        y: pointer.y - mousePointTo.y * newScale
      }

      stageRef.current!.position(newPos)
      stageRef.current!.scale({ x: newScale, y: newScale })
    } else if (e.evt.ctrlKey) {
      // X轴平移
      lastStageXRef.current = stageXRef.current
      const newLayerX = stageXRef.current - deltaY
      stageXRef.current = newLayerX
      setStageX(stageXRef.current)
    } else {
      // Y轴平移
      lastStageYRef.current = stageYRef.current
      const newLayerY = stageYRef.current - deltaY
      stageYRef.current = newLayerY
      setStageY(stageYRef.current)
    }
  }

  // 限制元素移出X轴
  useEffect(() => {
    const isOnScreen = stageRef.current.isClientRectOnScreen()
    if (!isOnScreen) {
      stageXRef.current = lastStageXRef.current
      setStageX(stageXRef.current)
    }
  }, [stageX])

  // 限制元素移出Y轴
  useEffect(() => {
    const isOnScreen = stageRef.current.isClientRectOnScreen()
    if (!isOnScreen) {
      stageYRef.current = lastStageYRef.current
      setStageY(stageYRef.current)
    }
  }, [stageY])

  const handleOnMouseDown = (evt: Konva.KonvaEventObject<MouseEvent>) => {
    mouseClickRef.current = true
    // 鼠标中键
    if (evt.evt.button === 1 || (mouseClickRef.current && isCursorGrab())) {
      movingStage(evt)
    }
  }

  const handleOnMouseMove = (evt: Konva.KonvaEventObject<MouseEvent>) => {
    if (isCursorGrab() && mouseClickRef.current) {
      const { x, y } = evt.evt
      const diffX = x - wheelStartXRef.current
      const diffY = y - wheelStartYRef.current

      stageXRef.current = startStageXRef.current + diffX
      stageYRef.current = startStageYRef.current + diffY

      setStageX(stageXRef.current)
      setStageY(stageYRef.current)
    }
  }

  const handleOnMouseUp = () => {
    setCursorStatus('cursor-auto')
    mouseClickRef.current = false
  }

  // 移动画布
  const movingStage = (evt: Konva.KonvaEventObject<MouseEvent>) => {
    setCursorStatus('cursor-grab')
    const { x, y } = evt.evt
    wheelStartXRef.current = x
    wheelStartYRef.current = y

    const { x: startStageX, y: startStageY } = stageRef.current.getClientRect()
    startStageXRef.current = startStageX
    startStageYRef.current = startStageY
  }

  const handleKeyDown = e => {
    if (e.code === 'Space' && cursorStatus === 'cursor-auto') {
      setCursorStatus('cursor-grab')
    }
  }

  const handleKeyUp = e => {
    if (e.code === 'Space' && isCursorGrab()) {
      setCursorStatus('cursor-auto')
    }
  }

  // 鼠标是手型
  const isCursorGrab = () => {
    return cursorStatus === 'cursor-grab'
  }

  return (
    <div className='bg-sky-200 outline-none' tabIndex={1} onKeyDown={handleKeyDown} onKeyUp={handleKeyUp}>
      <Stage
        ref={stageRef}
        width={window.innerWidth}
        height={window.innerHeight}
        className={cursorStatus}
        onMouseDown={handleOnMouseDown}
        onMouseMove={handleOnMouseMove}
        onMouseUp={handleOnMouseUp}
        onMouseLeave={handleOnMouseUp}
        onWheel={handleWheel}
        x={stageX}
        y={stageY}
      >
        <Layer>
          <Text fontSize={20} text='实现photoshop中平移画布的功能' />
          <Text y={80} fontSize={20} text='supports both zooming and panning' />
          {stars.map(star => (
            <Star
              key={star.id}
              id={star.id}
              x={star.x}
              y={star.y}
              numPoints={5}
              innerRadius={20}
              outerRadius={40}
              fill='#89b717'
              opacity={0.8}
              draggable
              rotation={star.rotation}
              shadowColor='black'
              shadowBlur={10}
              shadowOpacity={0.6}
              shadowOffsetX={star.isDragging ? 10 : 5}
              shadowOffsetY={star.isDragging ? 10 : 5}
              scaleX={star.isDragging ? 1.2 : 1}
              scaleY={star.isDragging ? 1.2 : 1}
              onDragStart={handleDragStart}
              onDragEnd={handleDragEnd}
            />
          ))}
        </Layer>
      </Stage>
    </div>
  )
}
